#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import json
import os
import re
import sys
import urllib.request
import urllib.error
from html.parser import HTMLParser
from typing import Any, Dict, List, Optional, Tuple
import base64


BASE_API = "http://43.153.150.144:8000"
BASE_UI = "http://43.153.150.144:8501/credentials"

EXPORT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "exports", "credentials_md")


class OptionParser(HTMLParser):
    """Simple HTML option extractor for <select>.. <option value="..">text</option>."""

    def __init__(self) -> None:
        super().__init__()
        self.in_option = False
        self.current_value: Optional[str] = None
        self.current_text_parts: List[str] = []
        self.options: List[Tuple[str, str]] = []

    def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
        if tag.lower() == "option":
            self.in_option = True
            value = None
            for k, v in attrs:
                if k.lower() == "value":
                    value = v
                    break
            self.current_value = value
            self.current_text_parts = []

    def handle_endtag(self, tag: str) -> None:
        if tag.lower() == "option" and self.in_option:
            text = "".join(self.current_text_parts).strip()
            value = (self.current_value or text).strip()
            if value:
                self.options.append((value, text or value))
            self.in_option = False
            self.current_value = None
            self.current_text_parts = []

    def handle_data(self, data: str) -> None:
        if self.in_option:
            self.current_text_parts.append(data)


def build_auth_header() -> Dict[str, str]:
    user = os.environ.get("BASIC_AUTH_USER")
    pwd = os.environ.get("BASIC_AUTH_PASS")
    if user and pwd:
        token = base64.b64encode(f"{user}:{pwd}".encode("utf-8")).decode("utf-8")
        return {"Authorization": f"Basic {token}"}
    return {}


def http_get_json(url: str, timeout: int = 20, headers: Optional[Dict[str, str]] = None) -> Any:
    req = urllib.request.Request(url, headers=headers or {})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        charset = resp.headers.get_content_charset() or "utf-8"
        data = resp.read().decode(charset, errors="replace")
        return json.loads(data)


def http_get_text(url: str, timeout: int = 20) -> str:
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        charset = resp.headers.get_content_charset() or "utf-8"
        return resp.read().decode(charset, errors="replace")


def fetch_connectors_from_page() -> List[str]:
    try:
        html = http_get_text(BASE_UI, timeout=20)
    except Exception:
        return []

    # Try parse <option> tags
    parser = OptionParser()
    try:
        parser.feed(html)
    except Exception:
        pass

    candidates = [v.strip() for v, t in parser.options if v.strip()]
    # Fallback regex in case options are rendered differently
    if not candidates:
        for m in re.finditer(r"<option[^>]*value=\"([^\"]+)\"[^>]*>([^<]*)</option>", html, flags=re.I):
            val = (m.group(1) or m.group(2) or "").strip()
            if val:
                candidates.append(val)
    # Deduplicate, keep order
    seen = set()
    result: List[str] = []
    for c in candidates:
        if c not in seen:
            seen.add(c)
            result.append(c)
    return result


def fetch_connectors_from_api() -> List[str]:
    url = f"{BASE_API}/connectors/"
    try:
        data = http_get_json(url, headers=build_auth_header())
        if isinstance(data, list):
            return [str(x) for x in data]
        # Some APIs return {"connectors": [...]} shape
        if isinstance(data, dict) and isinstance(data.get("connectors"), list):
            return [str(x) for x in data["connectors"]]
    except Exception:
        return []
    return []


def fetch_config_map(connector_name: str) -> Dict[str, Any]:
    url = f"{BASE_API}/connectors/{connector_name}/config-map"
    try:
        data = http_get_json(url, headers=build_auth_header())
        if isinstance(data, dict):
            return data
    except Exception:
        pass
    return {}


def normalize_fields_from_config(config_map: Dict[str, Any]) -> List[Dict[str, Any]]:
    """Produce a list of credential fields with name, required, type, description.

    Accept several shapes:
    1) JSON-schema like: { properties: { k: {type, description, title, ...} }, required: [k1, ...] }
    2) Flat map: { k: { type, description, title, required } }
    3) List: [ { name, type, description, required } ]
    """
    fields: List[Dict[str, Any]] = []

    if not isinstance(config_map, dict):
        return fields

    # Case 1: JSON Schema-like
    props = config_map.get("properties")
    if isinstance(props, dict):
        required_set = set(config_map.get("required") or [])
        for k, v in props.items():
            if not isinstance(v, dict):
                continue
            ftype = v.get("type") or (v.get("anyOf") and "anyOf") or ""
            desc = v.get("description") or v.get("title") or ""
            fields.append({
                "name": k,
                "required": bool(k in required_set or v.get("required")),
                "type": ftype if isinstance(ftype, str) else str(ftype),
                "description": desc,
            })
        return fields

    # Case 2: Flat map
    is_flat = all(isinstance(v, dict) for v in config_map.values()) if config_map else False
    if is_flat:
        for k, v in config_map.items():
            if not isinstance(v, dict):
                continue
            ftype = v.get("type") or ""
            desc = v.get("description") or v.get("title") or ""
            req = v.get("required")
            fields.append({
                "name": k,
                "required": bool(req),
                "type": ftype if isinstance(ftype, str) else str(ftype),
                "description": desc,
            })
        return fields

    # Case 3: List of field definitions
    if isinstance(config_map, list):
        for item in config_map:
            if not isinstance(item, dict):
                continue
            name = item.get("name") or item.get("key") or ""
            if not name:
                continue
            ftype = item.get("type") or ""
            desc = item.get("description") or item.get("title") or ""
            req = item.get("required")
            fields.append({
                "name": str(name),
                "required": bool(req),
                "type": ftype if isinstance(ftype, str) else str(ftype),
                "description": desc,
            })
        return fields

    return fields


def to_slug(name: str) -> str:
    slug = re.sub(r"[^a-zA-Z0-9_-]+", "-", name.strip())
    slug = re.sub(r"-+", "-", slug).strip("-")
    return slug.lower() or "connector"


def build_env_block(connector: str, fields: List[Dict[str, Any]]) -> str:
    prefix = to_slug(connector).upper().replace("-", "_")
    lines = ["# 可选：将凭证放入环境变量中，部署时注入", "# 注意：仅示例，具体加载机制取决于你的系统"]
    for f in fields:
        key = f.get("name", "").upper()
        if not key:
            continue
        lines.append(f"{prefix}_{key}=")
    return "\n".join(lines)


def build_curl_block(connector: str, fields: List[Dict[str, Any]]) -> str:
    example_obj = {f.get("name", ""): "<填写>" for f in fields if f.get("name")}
    body = json.dumps(example_obj, ensure_ascii=False, indent=2)
    return (
        "# 通过 API 添加凭证示例\n"
        f"curl -X POST \"{BASE_API}/accounts/add-credential/<account_name>/{connector}\" "
        "-H 'Content-Type: application/json' "
        f"-d '{body}'\n"
        "# 将 <account_name> 替换为你的账号标识\n"
    )


def render_markdown(connector: str, fields: List[Dict[str, Any]]) -> str:
    title = f"{connector} 连接器凭证配置说明"
    md: List[str] = []
    md.append(f"## {title}")
    md.append("")
    md.append(f"- 来源页面：`{BASE_UI}`")
    md.append(f"- 参考 API：`{BASE_API}/docs`，`{BASE_API}/openapi.json`")
    md.append("")

    if not fields:
        md.append("> 注意：未从配置中解析到字段，请参考后端文档或产品说明。")
    else:
        md.append("### 凭证字段")
        md.append("")
        md.append("| 名称 | 位置 | 必填 | 类型 | 中文说明 |")
        md.append("| --- | --- | --- | --- | --- |")
        for f in fields:
            name = f.get("name", "")
            required = "是" if f.get("required") else "否"
            ftype = str(f.get("type") or "")
            desc = f.get("description") or f"{name} 字段，用于 {connector} 的认证。"
            # 位置：凭证一般提交在请求体中，这里默认 body
            location = "body"
            md.append(f"| {name} | {location} | {required} | {ftype} | {desc} |")
        md.append("")

    md.append("### 示例：环境变量片段")
    md.append("")
    md.append("```bash")
    md.append(build_env_block(connector, fields))
    md.append("```")
    md.append("")

    md.append("### 示例：添加凭证 API 调用")
    md.append("")
    md.append("```bash")
    md.append(build_curl_block(connector, fields))
    md.append("```")
    md.append("")

    md.append("### 常见错误与排查")
    md.append("")
    md.append("- 凭证无效：检查密钥是否对应正确账户与权限范围。")
    md.append("- 时间偏差：若接口报签名/时间戳错误，校准服务器时间。")
    md.append("- 网络受限：若访问失败，检查防火墙与代理设置。")
    md.append("")

    return "\n".join(md)


def write_file(path: str, content: str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(content)


def main() -> int:
    # 1) 获取 Connector 列表
    connectors = fetch_connectors_from_page()
    if not connectors:
        connectors = fetch_connectors_from_api()
    if not connectors:
        print("[ERROR] 无法获取 Connector 列表。请检查页面或 API 可用性。")
        return 2

    # 2) 为每个 Connector 取配置与生成文档
    os.makedirs(EXPORT_DIR, exist_ok=True)
    generated: List[str] = []
    for name in connectors:
        config = fetch_config_map(name)
        fields = normalize_fields_from_config(config)
        md = render_markdown(name, fields)
        fname = f"{to_slug(name)}.md"
        fpath = os.path.join(EXPORT_DIR, fname)
        write_file(fpath, md)
        generated.append(fname)

    # 3) 写 README 索引
    lines = ["## 连接器凭证索引", ""]
    for fname in generated:
        title = fname.rsplit(".", 1)[0]
        lines.append(f"- [{title}]({fname})")
    write_file(os.path.join(EXPORT_DIR, "README.md"), "\n".join(lines))

    print(f"Generated {len(generated)} markdown files at: {EXPORT_DIR}")
    for p in generated[:10]:
        print(f" - {p}")
    return 0


if __name__ == "__main__":
    sys.exit(main())


